<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>WebGL - 2D image processing</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" media="screen" href="../resources/webgl-tutorials.css">
    <style>
        #ui {
            font-size: x-small;
        }

        table {
            border-collapse: collapse;
        }

        .tDnD_whileDrag {
            background-color: #daf;
        }

        tr:nth-child(odd) {
            background-color: #eee
        }

        tr:nth-child(even) {
            background-color: #def;
        }

        td {
            border-width: 0px;
            padding: 0px;
            margin: 0px;
        }
    </style>
</head>

<body>
    <div id="info">
        <a href="https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-image-processing-continued.html"
            target="_blank">WebGL 进一步处理图像</a>
    </div>
    <canvas id="canvas"></canvas>
    <div id="uiContainer">
        <div id="ui"></div>
    </div>
</body>
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;

uniform vec2 u_resolution;
uniform float u_flipY;

varying vec2 v_texCoord;

void main(){
    // 从像素坐标转换0.0到1.0
    vec2 zeroToOne = a_position / u_resolution;
    // 再把0->1转换0->2
    vec2 zeroToTwo = zeroToOne * 2.0;
    // 把0->2转换到-1->+1
    vec2 clipSpace = zeroToTwo - 1.0;

    gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);

    v_texCoord = a_texCoord;
}
</script>

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];
uniform float u_kernelWeight;

varying vec2 v_texCoord;

void main(){
    vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
    vec4 colorSum = 
        texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
        texture2D(u_image, v_texCoord + onePixel * vec2(-1,  0)) * u_kernel[3] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 0,  0)) * u_kernel[4] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 1,  0)) * u_kernel[5] +
        texture2D(u_image, v_texCoord + onePixel * vec2(-1,  1)) * u_kernel[6] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 0,  1)) * u_kernel[7] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 1,  1)) * u_kernel[8] ;

    gl_FragColor = vec4((colorSum / u_kernelWeight).rgb, 1);
}
</script>
<script src="../resources/jquery-1.7.1.min.js"></script>
<script src="../resources/jquery.tablednd_0_5.js"></script>
<script src="../resources/webgl-utils.js"></script>
<script>
    function main() {
        var image = new Image();
        image.src = "../resources/leaves.jpg";
        image.onload = function () {
            render(image);
        };
    }

    function render(image) {
        var canvas = document.getElementById("canvas");
        /** @type {WebGLRenderingContext}*/
        var gl = canvas.getContext("webgl");
        if (!gl) {
            return;
        }

        var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

        var positionLocation = gl.getAttribLocation(program, "a_position");
        var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");

        var positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        setRectangle(gl, 0, 0, image.width, image.height);

        var texcoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            0.0, 0.0,
            1.0, 0.0,
            0.0, 1.0,
            0.0, 1.0,
            1.0, 0.0,
            1.0, 1.0,
        ]), gl.STATIC_DRAW);

        function createAndSetupTexture(gl) {
            var texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, texture);

            //设置材质,这样可以对任意大小的图像进行像素操作
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            return texture;
        }
        //创建一个纹理并写入图像
        var originalImageTexture = createAndSetupTexture(gl);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        //创建两个纹理绑定到帧缓冲
        var textures = [];
        var framebuffers = [];
        for (let ii = 0; ii < 2; ++ii) {
            var texture = createAndSetupTexture(gl);
            textures.push(texture);
            //设置纹理大小和图像大小一致
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
            //创建一个帧缓冲
            var fbo = gl.createFramebuffer();
            framebuffers.push(fbo);
            gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
            //绑定纹理到帧缓冲
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
        }

        var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
        var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
        var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
        var kernelWeightLocation = gl.getUniformLocation(program, "u_kernelWeight");
        var flipYLocation = gl.getUniformLocation(program, "u_flipY");

        // 定义一些卷积核
        var kernels = {
            normal: [
                0, 0, 0,
                0, 1, 0,
                0, 0, 0
            ],
            // 高斯模糊
            gaussianBlur: [
                0.045, 0.122, 0.045,
                0.122, 0.332, 0.122,
                0.045, 0.122, 0.045
            ],
            gaussianBlur2: [
                1, 2, 1,
                2, 4, 2,
                1, 2, 1
            ],
            gaussianBlur3: [
                0, 1, 0,
                1, 1, 1,
                0, 1, 0
            ],
            //非锐化
            unsharpen: [
                -1, -1, -1,
                -1, 9, -1,
                -1, -1, -1
            ],
            sharpness: [
                0, -1, 0,
                -1, 5, -1,
                0, -1, 0
            ],
            //锐化
            sharpen: [
                -1, -1, -1,
                -1, 16, -1,
                -1, -1, -1
            ],
            //边缘检测
            edgeDetect: [
                -0.125, -0.125, -0.125,
                -0.125, 1, -0.125,
                -0.125, -0.125, -0.125
            ],
            edgeDetect2: [
                -1, -1, -1,
                -1, 8, -1,
                -1, -1, -1
            ],
            edgeDetect3: [
                -5, 0, 0,
                0, 0, 0,
                0, 0, 5
            ],
            edgeDetect4: [
                -1, -1, -1,
                0, 0, 0,
                1, 1, 1
            ],
            edgeDetect5: [
                -1, -1, -1,
                2, 2, 2,
                -1, -1, -1
            ],
            edgeDetect6: [
                -5, -5, -5,
                -5, 39, -5,
                -5, -5, -5
            ],
            sobelHorizontal: [
                1, 2, 1,
                0, 0, 0,
                -1, -2, -1
            ],
            sobelVertical: [
                1, 0, -1,
                2, 0, -2,
                1, 0, -1
            ],
            previtHorizontal: [
                1, 1, 1,
                0, 0, 0,
                -1, -1, -1
            ],
            previtVertical: [
                1, 0, -1,
                1, 0, -1,
                1, 0, -1
            ],
            boxBlur: [
                0.111, 0.111, 0.111,
                0.111, 0.111, 0.111,
                0.111, 0.111, 0.111
            ],
            triangleBlur: [
                0.0625, 0.125, 0.0625,
                0.125, 0.25, 0.125,
                0.0625, 0.125, 0.0625
            ],
            emboss: [
                -2, -1, 0,
                -1, 1, 1,
                0, 1, 2
            ]
        };
        // 将要使用的效果列表
        var effects = [
            { name: "gaussianBlur3", on: true },
            { name: "gaussianBlur3", on: true },
            { name: "gaussianBlur3", on: true },
            { name: "sharpness", },
            { name: "sharpness", },
            { name: "sharpness", },
            { name: "sharpen", },
            { name: "sharpen", },
            { name: "sharpen", },
            { name: "unsharpen", },
            { name: "unsharpen", },
            { name: "unsharpen", },
            { name: "emboss", on: true },
            { name: "edgeDetect", },
            { name: "edgeDetect", },
            { name: "edgeDetect3", },
            { name: "edgeDetect3", },
        ];
        // 创建特效选择栏
        var ui = document.getElementById("ui");
        var table = document.createElement("table");
        var tbody = document.createElement("tbody");
        for (var ii = 0; ii < effects.length; ++ii) {
            var effect = effects[ii];
            var tr = document.createElement("tr");
            var td = document.createElement("td");
            var chk = document.createElement("input");
            chk.value = effect.name;
            chk.type = "checkbox";
            if (effect.on) {
                chk.checked = "true";
            }
            chk.onchange = drawEffects;
            td.appendChild(chk);
            td.appendChild(document.createTextNode(effect.name));
            tr.appendChild(td);
            tbody.appendChild(tr);
        }
        table.appendChild(tbody);
        ui.appendChild(table);
        $("#ui table").tableDnD({ onDrop: drawEffects });

        drawEffects();

        function computeKernelWeight(kernel) {
            var weight = kernel.reduce(function (prev, curr) {
                return prev + curr;
            });
            return weight <= 0 ? 1 : weight;
        }

        function drawEffects(name) {
            webglUtils.resizeCanvasToDisplaySize(gl.canvas);

            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);
            // Tell it to use our program (pair of shaders)
            gl.useProgram(program);

            gl.enableVertexAttribArray(positionLocation);

            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

            // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
            gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

            gl.enableVertexAttribArray(texcoordLocation);
            gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);

            // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
            gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0);

            gl.uniform2f(textureSizeLocation, image.width, image.height);

            // 从原始图像开始
            gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);
            // 在渲染效果时不翻转y轴
            gl.uniform1f(flipYLocation, 1);
            // 循环施加每一种渲染效果
            var count = 0;
            for (var ii = 0; ii < tbody.rows.length; ++ii) {
                var checkbox = tbody.rows[ii].firstChild.firstChild;
                if (checkbox.checked) {
                    //使用两个帧缓冲中的一个
                    setFramebuffer(framebuffers[count % 2], image.width, image.height);
                    drawWithKernel(checkbox.value);
                    //下次绘制时使用刚才的渲染结果
                    gl.bindTexture(gl.TEXTURE_2D, textures[count % 2]);
                    ++count;
                }
            }
            // 最后将结果绘制到画布
            gl.uniform1f(flipYLocation, -1);// 需要绕y轴翻转
            setFramebuffer(null, gl.canvas.width, gl.canvas.height);
            drawWithKernel("normal");
        }

        function setFramebuffer(fbo, width, height) {
            // 设定当前使用帧缓冲
            gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
            // 告诉着色器分辨率是多少
            gl.uniform2f(resolutionLocation, width, height);
            // 告诉WebGL帧缓冲需要的视图大小
            gl.viewport(0, 0, width, height);
        }

        function drawWithKernel(name) {
            // 设置卷积核
            gl.uniform1fv(kernelLocation, kernels[name]);
            gl.uniform1f(kernelWeightLocation, computeKernelWeight(kernels[name]));

            // 画出矩形
            var primitiveType = gl.TRIANGLES;
            var offset = 0;
            var count = 6;
            gl.drawArrays(primitiveType, offset, count);
        }
    }

    function setRectangle(gl, x, y, width, height) {
        var x1 = x;
        var x2 = x + width;
        var y1 = y;
        var y2 = y + height;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            x1, y1,
            x2, y1,
            x1, y2,
            x1, y2,
            x2, y1,
            x2, y2,
        ]), gl.STATIC_DRAW);
    }

    main();

</script>

</html>